import errno
import json
import paramiko
import os
import logging
import time
import stat
from typing import List
def setup_logger(logger_name='my_logger'):
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.INFO)
    
    # 避免重复添加处理器
    if not logger.handlers:
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        
        # 添加文件处理器
        file_handler = logging.FileHandler(time.strftime('%Y-%m-%d')+'.log')
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
        
        # 添加控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)
    return logger
class SftpTransfer:
    def __init__(self):
        # 源sftp服务器配置
        self.source_host = None
        self.source_port = 22
        self.source_user = None
        self.source_password = None
        self.source_dir = None
        
        # 目标sftp服务器配置
        self.target_host = None
        self.target_port = 22
        self.target_user = None
        self.target_password = None
        self.target_dir = None
        
        self.delete_after_transfer = True
        self.logger = setup_logger('SftpTransfer')

    def set_source_server(self, host: str, port: int = 22, user: str = None, password: str = None):
        self.source_host = host
        self.source_port = port
        self.source_user = user
        self.source_password = password

    def set_target_server(self, host: str, port: int = 22, user: str = None, password: str = None):
        self.target_host = host
        self.target_port = port
        self.target_user = user
        self.target_password = password

    def set_source_directory(self, directory: str):
        self.source_dir = directory

    def set_target_directory(self, directory: str):
        self.target_dir = directory

    def set_delete_after_transfer(self, delete: bool):
        self.delete_after_transfer = delete

    def _ensure_remote_dir_exists(self, sftp: paramiko.SFTPClient, remote_path: str):
        """确保远程目录存在，如果不存在则创建"""
        try:
            sftp.stat(remote_path)
        except FileNotFoundError:
            try:
                sftp.mkdir(remote_path)
                self.logger.info(f"创建远程目录: {remote_path}")
            except Exception as e:
                self.logger.error(f"创建远程目录失败: {remote_path} - {str(e)}")
                raise

    def _transfer_directory(self, source_sftp: paramiko.SFTPClient, target_sftp: paramiko.SFTPClient, 
                          source_path: str, target_path: str) -> bool:
        """递归传输目录及其内容"""
        try:
            # 确保目标目录存在
            self._ensure_remote_dir_exists(target_sftp, target_path)
            
            # 获取源目录内容
            for item in source_sftp.listdir(source_path):
                source_item_path = os.path.join(source_path, item).replace("\\", "/")
                target_item_path = os.path.join(target_path, item).replace("\\", "/")
                
                try:
                    # 获取文件属性
                    item_stat = source_sftp.stat(source_item_path)
                    
                    if stat.S_ISDIR(item_stat.st_mode):
                        # 如果是目录，递归处理
                        self.logger.info(f"处理子目录: {source_item_path}")
                        self._transfer_directory(source_sftp, target_sftp, source_item_path, target_item_path)
                    else:
                        # 如果是文件，直接传输
                        self.logger.info(f"传输文件: {source_item_path}")
                        success=self._copy_file(source_sftp, target_sftp, source_item_path, target_item_path)
                        # 如果设置了删除选项，并且复制成功，则删除源文件
                        if self.delete_after_transfer and success:
                            self._delete_file(source_sftp, source_item_path)
                            
                except Exception as e:
                    self.logger.error(f"处理项目失败: {source_item_path} - {str(e)}")
                    continue
                    
            # 如果设置了删除选项，删除空目录
            if self.delete_after_transfer:
                try:
                    # 如果目录为空，则删除目录，最初的目录不进行删除
                    if not source_sftp.listdir(source_path) and source_path != self.source_dir:
                        source_sftp.rmdir(source_path)
                        self.logger.info(f"删除空目录: {source_path}")
                except Exception as e:
                    self.logger.error(f"删除目录失败: {source_path} - {str(e)}")
                    
            return True
            
        except Exception as e:
            self.logger.error(f"传输目录失败: {source_path} - {str(e)}")
            return False

    def _copy_file(self, source_sftp: paramiko.SFTPClient, target_sftp: paramiko.SFTPClient, 
                  source_path: str, target_path: str) -> bool:
        """复制单个文件"""
        try:
            # 检查目标文件是否存在，如果存在则删除
            try:
                target_sftp.stat(target_path)
                self.logger.info(f"目标文件已存在，将被覆盖: {target_path}")
                try:
                    target_sftp.remove(target_path)
                except IOError as e:
                    if e.errno == errno.EACCES:
                        self.logger.warning(f"无法删除目标文件（权限不足）: {target_path}")
                        # 可以选择跳过此文件或使用其他策略
                        return False
                    raise
            except FileNotFoundError:
                pass  # 文件不存在，继续执行
            
            # 使用临时文件进行上传，完成后重命名，避免文件不完整
            temp_path = target_path + ".temp"
            
            with source_sftp.file(source_path, 'rb') as source_file:
                with target_sftp.file(temp_path, 'wb') as target_file:
                    # 复制文件内容
                    target_file.write(source_file.read())
            
            # 重命名临时文件为最终文件名
            target_sftp.rename(temp_path, target_path)
            
            self.logger.info(f"成功传输文件: {target_path}")
            return True
        except Exception as e:
            self.logger.error(f"传输文件失败: {target_path} - {str(e)}")
            return False

    def start_transfer(self):
        # 连接到源服务器
        source_ssh = self._create_ssh_connection(
            self.source_host, self.source_port, self.source_user, self.source_password
        )
        if not source_ssh:
            self.logger.error("无法连接到源服务器:{}:{}".format(self.source_host,self.source_port))
            return False
        
        # 连接到目标服务器
        target_ssh = self._create_ssh_connection(
            self.target_host, self.target_port, self.target_user, self.target_password
        )
        if not target_ssh:
            source_ssh.close()
            self.logger.error("无法连接到目标服务器:{}:{}".format(self.target_host,self.target_port))
            return False
        
        # 创建SFTP客户端
        source_sftp = source_ssh.open_sftp()
        target_sftp = target_ssh.open_sftp()
        
        try:
            # 确保源目录存在
            try:
                source_sftp.stat(self.source_dir)
            except FileNotFoundError:
                self.logger.error(f"源目录不存在: {self.source_dir}")
                return False
            
            # 开始传输
            self.logger.info(f"开始传输目录: {self.source_dir} -> {self.target_dir}")
            success = self._transfer_directory(source_sftp, target_sftp, self.source_dir, self.target_dir)
            
            if success:
                self.logger.info("目录传输完成")
            else:
                self.logger.error("目录传输失败")
            
            return success
            
        except Exception as e:
            self.logger.error(f"传输过程中发生错误: {str(e)}")
            return False
            
        finally:
            # 关闭连接
            source_sftp.close()
            target_sftp.close()
            source_ssh.close()
            target_ssh.close()

    def _create_ssh_connection(self, host: str, port: int, user: str, password: str) -> paramiko.SSHClient:
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # 注意：生产环境应使用更安全的策略
            ssh.connect(host, port, user, password, timeout=10)
            self.logger.info(f"成功连接到 {host}:{port}")
            return ssh
        except Exception as e:
            self.logger.error(f"连接失败 {host}:{port} - {str(e)}")
            return None

    def load_config(self, config_file):
        """加载配置文件"""
        with open(config_file, 'r') as f:
            return json.load(f)
        
    def _delete_file(self, sftp: paramiko.SFTPClient, file_name: str) -> bool:
        try:
            sftp.remove(file_name)
            self.logger.info(f"已删除源文件: {file_name}")
            return True
        except Exception as e:
            self.logger.error(f"删除文件失败: {file_name} - {str(e)}")
            return False
def  main():
    try:
        # 你的主程序代码
        transfer = SftpTransfer()
        config_file = 'config.json'
        configs = transfer.load_config(config_file)
        for config in configs:
            source_sftp_config = config['source_sftp']
            dest_sftp_config = config['destination_sftp']
            # 设置源服务器
            transfer.set_source_server(
                host=source_sftp_config['hostname'],
                port=source_sftp_config['port'],
                user=source_sftp_config['username'],
                password=source_sftp_config['password']
            )
            
            # 设置目标服务器
            transfer.set_target_server(
                host=dest_sftp_config['hostname'],
                user=dest_sftp_config['username'],
                port=dest_sftp_config['port'],
                password=dest_sftp_config['password']
            )
            
            # 设置目录
            transfer.set_source_directory(source_sftp_config['remote_path'])
            transfer.set_target_directory(dest_sftp_config['remote_path'])
            
            # 设置是否删除源文件
            transfer.set_delete_after_transfer(source_sftp_config['delete_after_transfer'])
            # 执行传输
            success =transfer.start_transfer()
            print(f"传输结果: {'完成' if success else '失败'}")    
    except Exception as e:
        print(f"错误: {str(e)}")
    # finally:
    #     input("按回车键退出...")  # 暂停程序，等待用户输入
# 使用示例
if __name__ == "__main__":
        while True:
            try:
                main()
                # print("等待下一次执行...")
                time.sleep(5)  # 每5分钟执行一次
            except KeyboardInterrupt:
                print("\n程序已停止")
                break
            except Exception as e:
                print(f"发生错误: {str(e)}")
                time.sleep(60)  # 发生错误时等待1分钟后重试
    # source sftp_env/Scripts/activate
    # pyinstaller -F -i app.ico --add-data "config.json;." sftp_transfer.py